/**********************************************************************

  Audacity: A Digital Audio Editor

  VSTInstance.cpp

  Dominic Mazzoni

  Paul Licameli split from VSTEffect.cpp


*//********************************************************************/
#include "VSTInstance.h"

#include <wx/time.h>

std::unique_ptr<EffectInstance::Message> VSTInstance::MakeMessage() const
{
    // The purpose here is just to allocate vectors (chunk and paramVector)
    // with sufficient size, not to get the values too
    VSTSettings settings;
    FetchSettings(settings, /* doFetch = */ false);

    VSTMessage::ParamVector paramVector;
    paramVector.resize(mAEffect->numParams, std::nullopt);

    return std::make_unique<VSTMessage>(std::move(settings.mChunk), std::move(paramVector));
}

std::unique_ptr<EffectInstance::Message> VSTInstance::MakeMessage(int id, double value) const
{
    return std::make_unique<VSTMessage>(id, value, mAEffect->numParams);
}

unsigned VSTInstance::GetAudioInCount() const
{
    return mAudioIns;
}

unsigned VSTInstance::GetAudioOutCount() const
{
    return mAudioOuts;
}

size_t VSTInstance::SetBlockSize(size_t maxBlockSize)
{
    // Issue 3935 for IEM plug-ins, VST 2 versions:
    // It is mysterious why this further limitation of size works to
    // prevent the crashes in destructive processing, or why this is not
    // needed for non-destructive, but here it is
    // Those plugins report many channels (like 64) but most others will not
    // be affected by these lines with the default size of 8192
    // Note it may make the Block Size option of the settings dialog misleading
    auto numChannels = std::max({ 1u, GetAudioInCount(), GetAudioOutCount() });
    maxBlockSize = std::max(size_t(1),
                            std::min(maxBlockSize, size_t(0x8000u / numChannels)));

    mBlockSize = std::min(maxBlockSize, mUserBlockSize);
    return mBlockSize;
}

size_t VSTInstance::GetBlockSize() const
{
    return mBlockSize;
}

auto VSTInstance::GetLatency(
    const EffectSettings& settings, double sampleRate) const -> SampleCount
{
    if (mUseLatency) {
        return mBufferDelay;
    }
    return 0;
}

bool VSTInstance::IsReady()
{
    return mReady;
}

bool VSTInstance::ProcessInitialize(
    EffectSettings& settings, double sampleRate, ChannelNames)
{
    // Issue 3942: Copy the contents of settings first.
    // settings may refer to what is in the RealtimeEffectState, but that might
    // get reassigned by EffectSettingsAccess::Set, when the validator's
    // Automate() is called-back by the plug-in during callSetParameter.
    // So this avoids a dangling reference.
    auto copiedSettings = GetSettings(settings);
    StoreSettings(copiedSettings);

    return DoProcessInitialize(sampleRate);
}

bool VSTInstance::DoProcessInitialize(double sampleRate)
{
    // Initialize time info
    memset(&mTimeInfo, 0, sizeof(mTimeInfo));
    mTimeInfo.sampleRate = sampleRate;
    mTimeInfo.nanoSeconds = wxGetUTCTimeMillis().ToDouble();
    mTimeInfo.tempo = 120.0;
    mTimeInfo.timeSigNumerator = 4;
    mTimeInfo.timeSigDenominator = 4;
    mTimeInfo.flags = kVstTempoValid | kVstNanosValid | kVstTransportPlaying;

    // Set processing parameters...power must be off for this
    callDispatcher(effSetSampleRate, 0, 0, NULL, sampleRate);
    callDispatcher(effSetBlockSize, 0, mBlockSize, NULL, 0.0);

    // Turn on the power
    PowerOn();

    // Set the initial buffer delay
    SetBufferDelay(mAEffect->initialDelay);

    mReady = true;
    return true;
}

bool VSTInstance::ProcessFinalize() noexcept
{
    return GuardedCall<bool>([&] {
        mReady = false;

        PowerOff();

        return true;
    });
}

size_t VSTInstance::ProcessBlock(EffectSettings&,
                                 const float* const* inBlock, float* const* outBlock, size_t blockLen)
{
    // Only call the effect if there's something to do...some do not like zero-length block
    if (blockLen) {
        // Go let the plugin moleste the samples
        callProcessReplacing(inBlock, outBlock, blockLen);

        // And track the position
        mTimeInfo.samplePos += (double)blockLen;
    }

    return blockLen;
}

bool VSTInstance::RealtimeInitialize(EffectSettings& settings, double sampleRate, size_t)
{
    // Temporarily disconnect from any validator, so that setting the chunk
    // does not cause Automate() callbacks (as some effects will do) that then
    // would send slider movement messages that might destroy information in
    // the settings.
    auto vr = valueRestorer(mpOwningValidator, (VSTUIWrapper*)nullptr);
    return ProcessInitialize(settings, sampleRate, {});
}

bool VSTInstance::RealtimeAddProcessor(EffectSettings& settings,
                                       EffectOutputs*, unsigned numChannels, float sampleRate)
{
    if (!mRecruited) {
        // Assign self to the first processor
        mRecruited = true;
        return true;
    }

    auto& effect = static_cast<const PerTrackEffect&>(mProcessor);
    auto slave = std::make_unique<VSTInstance>(
        const_cast<PerTrackEffect&>(effect),
        mPath, mBlockSize, mUserBlockSize, mUseLatency);

    slave->SetBlockSize(mBlockSize);

    if (!slave->ProcessInitialize(settings, sampleRate, ChannelNames())) {
        return false;
    }

    mSlaves.emplace_back(move(slave));
    return true;
}

bool VSTInstance::RealtimeFinalize(EffectSettings&) noexcept
{
    return GuardedCall<bool>([&]{
        if (mpOwningValidator) {
            mpOwningValidator->Flush();
        }

        mRecruited = false;

        for (const auto& slave : mSlaves) {
            slave->ProcessFinalize();
        }
        mSlaves.clear();

        return ProcessFinalize();
    });
}

bool VSTInstance::RealtimeSuspend()
{
    PowerOff();

    for (const auto& slave : mSlaves) {
        slave->PowerOff();
    }

    return true;
}

bool VSTInstance::RealtimeResume()
{
    PowerOn();

    for (const auto& slave : mSlaves) {
        slave->PowerOn();
    }

    return true;
}

bool VSTInstance::OnePresetWasLoadedWhilePlaying()
{
    return mPresetLoadedWhilePlaying.exchange(false);
}

void VSTInstance::DeferChunkApplication()
{
    std::lock_guard<std::mutex> guard(mDeferredChunkMutex);

    if (!mChunkToSetAtIdleTime.empty()) {
        ApplyChunk(mChunkToSetAtIdleTime);
        mChunkToSetAtIdleTime.resize(0);
    }
}

void VSTInstance::ApplyChunk(std::vector<char>& chunk)
{
    VstPatchChunkInfo info = {
        1, mAEffect->uniqueID, mAEffect->version, mAEffect->numParams, "" };

    const auto len = chunk.size();
    const auto data = chunk.data();

    callSetChunk(true, len, data, &info);
    for (auto& slave : mSlaves) {
        slave->callSetChunk(true, len, data, &info);
    }
}

bool VSTInstance::ChunkMustBeAppliedInMainThread() const
{
    // Some plugins (e.g. Melda) can not have their chunk set in the
    // audio thread, resulting in making the whole app hang.
    // This is why we defer the setting of the chunk in the main thread.

    const bool IsAudioThread = (mMainThreadId != std::this_thread::get_id());

    return IsAudioThread && mIsMeldaPlugin;
}

bool VSTInstance::UsesMessages() const noexcept
{
    return true;
}

bool VSTInstance::RealtimeProcessStart(MessagePackage& package)
{
    const bool applyChunkInMainThread = ChunkMustBeAppliedInMainThread();

    if (applyChunkInMainThread) {
        mDeferredChunkMutex.lock();
    }

    if (!package.pMessage) {
        return true;
    }

    auto& message = static_cast<VSTMessage&>(*package.pMessage);

    auto& chunk = message.mChunk;

    if (!chunk.empty()) {
        if (applyChunkInMainThread) {
            // Apply the chunk later
            //
            mChunkToSetAtIdleTime = chunk;
        } else {
            // Apply the chunk now
            ApplyChunk(chunk);
        }

        // Don't apply the chunk again until another message supplies a chunk
        chunk.resize(0);

        // Don't return yet.  Maybe some slider movements also accumulated after
        // the change of the chunk.

        const bool IsAudioThread = (mMainThreadId != std::this_thread::get_id());
        if (IsAudioThread) {
            // At the moment, the only reason why this method would be called in the audio thread,
            // is because a preset was loaded while playing

            mPresetLoadedWhilePlaying.store(true);
        }
    }

    assert(message.mParamsVec.size() == mAEffect->numParams);

    for (size_t paramID=0; paramID < mAEffect->numParams; paramID++) {
        if (message.mParamsVec[paramID]) {
            float val = (float)(*message.mParamsVec[paramID]);

            // set the change on the recruited "this" instance
            callSetParameter(paramID, val);

            // set the change on any existing slaves
            for (auto& slave : mSlaves) {
                slave->callSetParameter(paramID, val);
            }

            // clear the used info
            message.mParamsVec[paramID] = std::nullopt;
        }
    }

    return true;
}

size_t VSTInstance::RealtimeProcess(size_t group, EffectSettings& settings,
                                    const float* const* inbuf, float* const* outbuf, size_t numSamples)
{
    if (!mRecruited) {
        // unexpected!
        return 0;
    }

    wxASSERT(numSamples <= mBlockSize);

    if (group == 0) {
        // use the recruited "this" instance
        return ProcessBlock(settings, inbuf, outbuf, numSamples);
    } else if (group <= mSlaves.size()) {
        // use the slave which maps to the group
        return mSlaves[group - 1]->ProcessBlock(settings, inbuf, outbuf, numSamples);
    } else {
        return 0;
    }
}

bool VSTInstance::RealtimeProcessEnd(EffectSettings&) noexcept
{
    if (ChunkMustBeAppliedInMainThread()) {
        mDeferredChunkMutex.unlock();
    }

    return true;
}

void VSTInstance::NeedIdle()
{
    if (mpOwningValidator) {
        mpOwningValidator->NeedIdle();
    }
}

void VSTInstance::PowerOn()
{
    if (!mHasPower) {
        // Turn the power on
        callDispatcher(effMainsChanged, 0, 1, NULL, 0.0);

        // Tell the effect we're going to start processing
        if (mVstVersion >= 2) {
            callDispatcher(effStartProcess, 0, 0, NULL, 0.0);
        }

        // Set state
        mHasPower = true;
    }
}

void VSTInstance::PowerOff()
{
    if (mHasPower) {
        // Tell the effect we're going to stop processing
        if (mVstVersion >= 2) {
            callDispatcher(effStopProcess, 0, 0, NULL, 0.0);
        }

        // Turn the power off
        callDispatcher(effMainsChanged, 0, 0, NULL, 0.0);

        // Set state
        mHasPower = false;
    }
}

void VSTInstance::SizeWindow(int w, int h)
{
    if (mpOwningValidator) {
        mpOwningValidator->SizeWindow(w, h);
    }
}

void VSTInstance::SetBufferDelay(int samples)
{
    // We do not support negative delay
    if (samples >= 0 && mUseLatency) {
        mBufferDelay = samples;
    }

    return;
}

void VSTInstance::callProcessReplacing(const float* const* inputs,
                                       float* const* outputs, int sampleframes)
{
    mAEffect->processReplacing(mAEffect,
                               const_cast<float**>(inputs),
                               const_cast<float**>(outputs), sampleframes);
}

void VSTInstance::Automate(int index, float value)
{
    if (mMainThreadId != std::this_thread::get_id()) {
        return;
    }

    if (mpOwningValidator) {
        mpOwningValidator->Automate(index, value);
    }
}

VSTInstance::VSTInstance
(
    const PerTrackEffect& effect,
    const PluginPath& path,
    size_t blockSize,
    size_t userBlockSize,
    bool useLatency)

    : PerTrackEffect::Instance(effect)
    , VSTWrapper(path)
    , mUseLatency{useLatency}
{
    // what also happens in the effect ctor
    //
    memset(&mTimeInfo, 0, sizeof(mTimeInfo));
    mTimeInfo.samplePos = 0.0;
    mTimeInfo.sampleRate = 44100.0; // this is a bogus value, but it's only for the display
    mTimeInfo.nanoSeconds = wxGetUTCTimeMillis().ToDouble();
    mTimeInfo.tempo = 120.0;
    mTimeInfo.timeSigNumerator = 4;
    mTimeInfo.timeSigDenominator = 4;
    mTimeInfo.flags = kVstTempoValid | kVstNanosValid;

    mBlockSize = blockSize;
    mUserBlockSize = userBlockSize;

    Load();

    if (!IsReady()) {
        // Set some defaults since some VSTs need them...these will be reset when
        // normal or realtime processing begins
        mBlockSize = 8192;
        DoProcessInitialize(44100.0);
    }

    mIsMeldaPlugin = (mVendor == "MeldaProduction");
}

VSTInstance::~VSTInstance()
{
    PowerOff();
}

void VSTInstance::SetOwningValidator(VSTUIWrapper* vi)
{
    mpOwningValidator = vi;
}
